package com.stars.easyms.logger.support.logback.pattern;

import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.pattern.PatternLayoutEncoderBase;
import com.stars.easyms.logger.support.EasyMsOverride;
import com.stars.easyms.logger.support.logback.handler.EasyMsLoggingEvent;
import com.stars.easyms.base.util.ReflectUtil;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;

/**
 * <p>className: EasyMsPatternLayoutEncoder</p>
 * <p>description: 重写PatternLayoutEncoderBase类</p>
 *
 * @author guoguifang
 * @date 2019-08-21 14:27
 * @since 1.3.0
 */
@SuppressWarnings("unchecked")
public final class EasyMsPatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> implements EasyMsOverride {

    private final Method setParentMethod;

    private final Field immediateFlushField;

    private boolean isImmediateFlush = true;

    public EasyMsPatternLayoutEncoder() {
        super();
        this.immediateFlushField = FieldUtils.getField(LayoutWrappingEncoder.class, "immediateFlush", true);
        this.setParentMethod = ReflectUtil.getMatchingMethod(EasyMsPatternLayoutEncoder.class, "setParent", Appender.class);
    }

    public EasyMsPatternLayoutEncoder(LayoutWrappingEncoder layoutWrappingEncoder, Appender appender) {
        this();
        this.context = layoutWrappingEncoder.getContext();
        this.setCharset(layoutWrappingEncoder.getCharset());
        this.setEncoderParentAppender(appender);
        this.setImmediateFlush(getLayoutEncoderImmediateFlush(layoutWrappingEncoder));
        if (layoutWrappingEncoder instanceof PatternLayoutEncoderBase) {
            PatternLayoutEncoderBase patternLayoutEncoder = (PatternLayoutEncoderBase) layoutWrappingEncoder;
            this.setPattern(patternLayoutEncoder.getPattern());
            this.setOutputPatternAsHeader(patternLayoutEncoder.isOutputPatternAsHeader());
            this.start();
        } else {
            Layout layout = layoutWrappingEncoder.getLayout();
            if (layout instanceof PatternLayout && !(layout instanceof EasyMsPatternLayout)) {
                PatternLayout patternLayout = (PatternLayout) layout;
                EasyMsPatternLayout easyMsPatternLayout = new EasyMsPatternLayout();
                easyMsPatternLayout.setContext(patternLayout.getContext());
                easyMsPatternLayout.setPattern(patternLayout.getPattern());
                easyMsPatternLayout.setOutputPatternAsHeader(patternLayout.isOutputPatternAsHeader());
                easyMsPatternLayout.start();
                this.layout = easyMsPatternLayout;
            } else {
                this.layout = layout;
            }
            super.start();
        }
    }

    @Override
    public void setImmediateFlush(boolean immediateFlush) {
        super.setImmediateFlush(immediateFlush);
        this.isImmediateFlush = immediateFlush;
    }

    @Override
    public void start() {
        EasyMsPatternLayout easyMsPatternLayout = new EasyMsPatternLayout();
        easyMsPatternLayout.setContext(context);
        easyMsPatternLayout.setPattern(getPattern());
        easyMsPatternLayout.setOutputPatternAsHeader(outputPatternAsHeader);
        easyMsPatternLayout.start();
        this.layout = easyMsPatternLayout;
        super.start();
    }

    public byte[] encodes(EasyMsLoggingEvent event) {
        String txt = layout.doLayout(event);
        Charset charset = getCharset();
        return charset == null ? txt.getBytes() : txt.getBytes(charset);
    }

    public boolean isImmediateFlush() {
        return isImmediateFlush;
    }

    private void setEncoderParentAppender(Appender appender) {
        if (setParentMethod != null) {
            try {
                setParentMethod.invoke(this, appender);
            } catch (IllegalAccessException | InvocationTargetException e) {
                // ignore
            }
        }
    }

    private boolean getLayoutEncoderImmediateFlush(LayoutWrappingEncoder layoutWrappingEncoder) {
        if (immediateFlushField != null) {
            try {
                Object obj = FieldUtils.readField(immediateFlushField, layoutWrappingEncoder);
                if (obj != null) {
                    return (boolean) obj;
                }
            } catch (IllegalAccessException e) {
                // ignore
            }
        }
        return true;
    }
}